package mqttclient

import (
	"strconv"

	MQTT "github.com/eclipse/paho.mqtt.golang"
)

type MqttClient struct {
	client        MQTT.Client
	clientId      string
	hostName      string
	port          int
	onMessage     []func(clientId string, topic string, payload string)
	onPublish     []func(clientId string, topic string, payload string)
	onSubscribe   []func(clientId string, topic string)
	onConnect     []func(clientId string)
	onUnsubscribe []func(clientId string, topic string)
	onDisconnect  []func(string)
}

type Option func(*MqttClient)

func HostName(host string) Option {
	return func(m *MqttClient) {
		m.hostName = host
	}
}

func Port(p int) Option {
	return func(m *MqttClient) {
		m.port = p
	}
}

func Url(url string) Option {
	return func(m *MqttClient) {
		m.hostName = url
	}
}

func ClientId(id string) Option {
	return func(m *MqttClient) {
		m.clientId = id
	}
}

func NewMqttClient(opts ...Option) *MqttClient {
	m := &MqttClient{
		hostName: "localhost",
		port:     1883,
	}

	for _, opt := range opts {
		opt(m)
	}

	broker := m.hostName + ":" + strconv.Itoa(m.port)
	option := MQTT.NewClientOptions().AddBroker(broker)
	option.SetClientID(m.clientId)

	option.SetOnConnectHandler(func(client MQTT.Client) {
		if len(m.onConnect) != 0 {
			for _, cb := range m.onConnect {
				cb(m.clientId)
			}
		}
	})
	option.SetConnectionLostHandler(func(client MQTT.Client, err error) {
		if len(m.onDisconnect) != 0 {
			for _, cb := range m.onDisconnect {
				cb(m.clientId)
			}
		}
	})

	m.client = MQTT.NewClient(option)
	return m
}

// func NewMqttClient(host string, clientId string, port ...int) *MqttClient {
// 	var p = 1883
// 	if len(port) > 0 {
// 		p = port[0]
// 	}

// 	broker := "tcp://" + host + ":" + strconv.Itoa(p)
// 	opts := MQTT.NewClientOptions().AddBroker(broker)
// 	opts.SetClientID(clientId)

// 	return &MqttClient{
// 		client:   MQTT.NewClient(opts),
// 		clientId: clientId,
// 	}
// }

func (m *MqttClient) Connect() error {
	if token := m.client.Connect(); token.Wait() && token.Error() != nil {
		return token.Error()
	}

	return nil
}

func (m *MqttClient) SetOnMessageCallback(callback func(clientId string, topic string, payload string)) *MqttClient {
	m.onMessage = append(m.onMessage, callback)
	return m
}

func (m *MqttClient) SetOnDisconnectCallback(cb func(string)) *MqttClient {
	m.onDisconnect = append(m.onDisconnect, cb)
	return m
}

func (m *MqttClient) Disconnect() {
	m.client.Disconnect(250)
}

func (m *MqttClient) IsConnected() bool {
	return m.client.IsConnected()
}

func (m *MqttClient) defaultMqttMessageCallback(c MQTT.Client, msg MQTT.Message) {

	if len(m.onMessage) != 0 {
		for _, cb := range m.onMessage {
			cb(m.clientId, msg.Topic(), string(msg.Payload()))
		}
	}
}

func (m *MqttClient) Publish(topic string, payload interface{}, qos ...int) bool {
	var q byte = 1
	if len(qos) > 0 {
		q = byte(qos[0])
	}

	if token := m.client.Publish(topic, q, false, payload); token.Wait() && token.Error() != nil {
		return false
	}

	if len(m.onPublish) != 0 {
		for _, cb := range m.onPublish {
			cb(m.clientId, topic, payload.(string))
		}
	}

	return true
}

func (m *MqttClient) Subscribe(topic string, qos ...int) *MqttClient {
	var q byte = 1
	if len(qos) > 0 {
		q = byte(qos[0])
	}

	m.client.Subscribe(topic, q, m.defaultMqttMessageCallback).Wait()

	if len(m.onSubscribe) != 0 {
		for _, cb := range m.onSubscribe {
			cb(m.clientId, topic)
		}
	}

	return m
}

func (m *MqttClient) Unsubscribe(topic string) *MqttClient {
	m.client.Unsubscribe(topic).Wait()

	if len(m.onUnsubscribe) != 0 {
		for _, cb := range m.onUnsubscribe {
			cb(m.clientId, topic)
		}
	}

	return m
}

func (m *MqttClient) SetPublishCallback(callback func(clientId string, topic string, payload string)) *MqttClient {
	m.onPublish = append(m.onPublish, callback)
	return m
}

func (m *MqttClient) SetSubscribeCallback(callback func(clientId string, topic string)) *MqttClient {
	m.onSubscribe = append(m.onSubscribe, callback)
	return m
}

func (m *MqttClient) SetConnectCallback(callback func(clientId string)) *MqttClient {
	m.onConnect = append(m.onConnect, callback)
	return m
}

func (m *MqttClient) SetUnsubscribeCallback(callback func(clientId string, topic string)) *MqttClient {
	m.onUnsubscribe = append(m.onUnsubscribe, callback)
	return m
}
